using System;
using System.Collections.Concurrent;
using System.Threading;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Primitives;

namespace Extensions.Configuration.Database
{
    /// <summary>
    /// 数据库监视
    /// A database watcher that watches a database for changes.
    /// </summary>
    public class DatabaseWatcher : IDisposable
    {
        private readonly Timer _timer;

        private bool _disposed;

        /// <summary>
        /// 轮询令牌
        /// (根据官方的设计这里应该是可以支持多个监控)
        /// </summary>
        internal ConcurrentDictionary<IPollingChangeToken, IPollingChangeToken> PollingChangeTokens { get; }

        /// <summary>
        /// 轮询间隔时间
        /// OPTIMIZE:改为可配置
        /// </summary>
        internal static TimeSpan DefaultPollingInterval => TimeSpan.FromSeconds(4);

        public DatabaseWatcher()
        {
            PollingChangeTokens = new ConcurrentDictionary<IPollingChangeToken, IPollingChangeToken>();
            _timer = new Timer(RaiseChangeEvents, PollingChangeTokens, TimeSpan.Zero, DefaultPollingInterval);
        }

        public IChangeToken CreateChangeToken(DbContextOptions<ConfigurationDatabaseContext> dbContextOptions)
        {
            // TODO:如果存在这个数据库就从缓存中取

            var pollingChangeToken = new DefaultPollingChangeToken(dbContextOptions)
            {
                ActiveChangeCallbacks = true,
                CancellationTokenSource = new CancellationTokenSource()
            };
            PollingChangeTokens.TryAdd(pollingChangeToken, pollingChangeToken);
            return pollingChangeToken;
        }

        internal static void RaiseChangeEvents(object state)
        {
            var changeTokens = (ConcurrentDictionary<IPollingChangeToken, IPollingChangeToken>)state;

            foreach (var item in changeTokens)
            {
                var token = item.Key;

                if (!token.HasChanged)
                {
                    continue;
                }

                if (!changeTokens.TryRemove(token, out _))
                {
                    // Move on if we couldn't remove the item.
                    continue;
                }

                // We're already on a background thread, don't need to spawn a background Task to cancel the CTS
                try
                {
                    token.CancellationTokenSource!.Cancel();
                }
                catch
                {
                    // ignored
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this._disposed)
            {
                if (disposing)
                {
                    this._timer?.Dispose();
                }

                this._disposed = true;
            }
        }
    }
}